package GA;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import java.lang.reflect.*;


import Operations.*;
import util.*;

public class ReflectionExecutor {
	private static final Logger logger = Logger.getLogger("ReflectionExecutor");
	/** The internal object repository */
	private final Map<String, Object> repositoryObjects;
	
	private final List<Operation> ops ;
	Operation[] inpSeq = new Operation[GAConfig.TestCaseSize];
	
	private final Map<String, String[]> repositoryType;
	private final Map<String, List<CreateObject>> constructors;

	public static  int execute(Chromosome test) {
		ReflectionExecutor executor = new ReflectionExecutor(test);

		return executor.execute();
	}

	public ReflectionExecutor(Chromosome test){
		repositoryType = test.getReferenceFactory();
		ops = test.getTest();
		this.constructors=test.getConstructors();
		repositoryObjects = new LinkedHashMap<String, Object>();

	}
	public int execute() {
		for(Operation op : ops) {
			try {
//				System.out.println("ReflectionExecutor.execute: "+ op.toString());
			if (op instanceof CreateObject) createReceiverObject((CreateObject) op);
			else if(op instanceof Invoke) invoke((Invoke) op,op);
				 else if(op instanceof AssignConstant) assignConstant((AssignConstant) op);
				 	  else logger.warning("ReflectionExecutor.execute: Unknown operation " + op.getClass().getName() + " - " + op);
			} catch(Throwable e) {
			//	System.err.println("ReflectionExecutor.execute:"+ e + op.getClass().getName() + " - " + op);
			}	
		}
		return 0;
	}
	private void createReceiverObject(CreateObject op){
		ConstructorDeclared cut = op.getConstructor();
		createReceiverObject(cut);
	}
	private void assignConstant(AssignConstant op){
		Class<?> clz=op.getClazz();
		String ref=op.getRefrence();
		String value=op.getValue();
		if(value == null) {
			repositoryObjects.put(ref, null);
		}else{
			try{
				//Field field = declaringClass.getField(value.getName());
			Field field = clz.getField(value);
			Object newObject = field.get(null);
			repositoryObjects.put(ref, newObject);
		} catch(Throwable e) {
			//System.err.println("ReflectionExecutor.assignConstant :"+ e.getMessage());
		}

		}
	}
	private void invoke(Invoke op,Operation operation) throws Throwable {
//		if(op.toString().contains(".equals"))
	
		final String receiver = op.getThis();
		final MethodDeclared method = op.getMethod();
		final String[] params = op.getParams();
		final Class<?>[] paramsTypes = method.getParameterTypes();
		
		Object receiverObject = null;
		if (!method.isStatic()) {
			receiverObject = (Object) getReceiverObject(receiver);
			if (receiverObject==null)  return;
		}
		// create parameters
		final Object[] args = new Object[params.length];
		for(int i = 0; i < args.length; i++) {
			Class<?> clz=paramsTypes[i];
			if(Filter.isObjectType(clz))
				 clz=method.getReplacementObjectParam();
			if(Filter.isPrimitiveOrStringType(clz))
				args[i] = ValuesGenerator.mapValueToObject(clz,params[i]);
			else 
				args[i]=(Object) getReceiverObject(params[i]);
		}
		// perform the method call
		//Object result = null;
		try {
			// get the Method object
			final Method m = getMethod(method);

			 m.invoke(receiverObject, args);
			return;

		} catch (InvocationTargetException e) {
	//		System.err.println(op.toString() + "InvocationTargetException."+ e.getMessage());
			return; // continues normal execution in case of exception in called
		}catch (IllegalAccessException e) {
			System.err.println("Illegal access error.");
			System.exit(1);
		}
		
	}
	public Method getMethod(MethodDeclared  m) throws ClassNotFoundException, SecurityException, NoSuchMethodException {

		Class<?> c = m.getClazz();
		Class<?>[] params = m.getParameterTypes();
		Method method = c.getMethod(m.getName(), params);

		return method;
	}

	private Object getReceiverObject(String objRef)  {
//		System.out.println("ReflectionExecutor.getReceiverObject: "+ objRef);

		Object obj = null;
		try {
			obj = repositoryObjects.get(objRef.toString());
			if (obj == null)
				obj = createReceiverObject(objRef);

		} catch (Throwable e) {

		}
		return obj;
	}
	/** createObject */
	private Object createReceiverObject(String objRef)  {
		Object obj = null;
		String cName=null;
		List<String> keys = new ArrayList<String>(repositoryType.keySet());   
		for(int i=0;i<keys.size()&&cName==null;i++){
			//repositoryType.get(keys.get(i)).toString();   
			String[] references = repositoryType.get(keys.get(i));
			Arrays.sort(references);
	    	if( Arrays.binarySearch(references, objRef)>=0)
				 cName =(String) keys.get(i).toString();
		}
		if(cName==null)	{
			System.out.println("Error -->ReflectionExecutor.createReceiverObject "+objRef + " is null");
			System.exit(0);
		}
		ConstructorDeclared cut=null;
		List<CreateObject> cutConst=constructors.get(cName);
		for(CreateObject Const: cutConst){
			cut= Const.getConstructor();
			if(cut.getRefrence().equals(objRef))
				break;
			else
				cut=null;
		}
		//int randNum=ValuesGenerator.getRandomInt(cutConst.length);
		// cut= cutConst[randNum];
		Class<?>[] paramsType = cut.getParameterTypes();
		Object[] actualParams = new Object[cut.getParameterTypes().length];
		String[] params = cut.getParams();
//		String[] p =cut.getParams();// new String[paramsType.length];
		for (int j = 0; j < actualParams.length; j++)
			actualParams[j] = ValuesGenerator.mapValueToObject(paramsType[j],params[j]);
		// get the Constructor object
		Constructor<?> cons=cut.getCons();
		try {
			obj = cons.newInstance(actualParams);

		} catch (Throwable e) {
		//	System.err.println("ReflectionExecutor.createReceiverObject execute Object Construction class: "+ cName+"  "+ e.getCause());
			//System.exit(1);
		}
		if(obj!=null)
			repositoryObjects.put(objRef, obj);
		return obj;

	}
	/** createObject from operation!*/
	private Object createReceiverObject(ConstructorDeclared cut)  {
		String objRef=cut.getRefrence();
		Object obj = null;
		
		Class<?>[] paramsType = cut.getParameterTypes();
		Object[] actualParams = new Object[cut.getParameterTypes().length];
		String[] params = cut.getParams();
//		String[] p =cut.getParams();// new String[paramsType.length];
		for (int j = 0; j < actualParams.length; j++)
			actualParams[j] = ValuesGenerator.mapValueToObject(paramsType[j],params[j]);
		// get the Constructor object
		Constructor<?> cons=cut.getCons();
		try {
			obj = cons.newInstance(actualParams);

		} catch (Throwable e) {
		//	System.err.println("ReflectionExecutor.createReceiverObject execute Object Construction class: "+ cName+"  "+ e.getCause());
			//System.exit(1);
		}
		if(obj!=null)
			repositoryObjects.put(objRef, obj);
		return obj;

	}
	public static String getReference(String cName,Map<String, String[]>  refFactory) {

		String refs[] = refFactory.get(cName);

		if(refs == null) return null;

		return refs[ValuesGenerator.getRandomInt(refs.length)];
	}


}
